#include "network/ConnectionManager.h"
#include <QtEndian>
#include <QErrorMessage>

#include "network/packet/Packets.h"

ConnectionManager::ConnectionManager() {
    tcpServer = new QTcpServer(this);
    socketIndexCounter = 0;
    mapperRead = mapperClose = NULL;
}

void ConnectionManager::init(QString address, int port) {
    if (tcpServer->listen(QHostAddress(address), port)) {
        qDebug() << "Listening on address " << address << ", port " << port;
    } else {
        QErrorMessage *error = new QErrorMessage();
        error->showMessage("Error while listening on port");
    }

    connect(tcpServer, SIGNAL(newConnection()), this, SLOT(connectionOpened()));
}

void ConnectionManager::connectionOpened() {
    QTcpSocket *clientSocket = tcpServer->nextPendingConnection();
    int socketIndex = socketIndexCounter++;
    socketMap[socketIndex] = new SocketData(clientSocket);

    qDebug() << "Connection opened " << clientSocket->localAddress() << ":"
             << clientSocket->localPort() << " <-> " << clientSocket->peerAddress()
             << ":" << clientSocket->peerPort();

    if (mapperRead == NULL) {
        mapperRead = new QSignalMapper(this);
        mapperClose = new QSignalMapper(this);
    }
    mapperRead->setMapping(clientSocket, socketIndex);
    mapperClose->setMapping(clientSocket, socketIndex);
    connect(clientSocket, SIGNAL(readyRead()), mapperRead, SLOT(map()));
    connect(clientSocket, SIGNAL(aboutToClose()), mapperClose, SLOT(map()));

    connect(mapperRead, SIGNAL(mapped(int)), this, SLOT(readData(int)));
    connect(mapperClose, SIGNAL(mapped(int)), this, SLOT(connectionClosed(int)));
}

void ConnectionManager::connectionClosed(int socketIndex) {
    SocketData *socketData = socketMap[socketIndex];
    socketMap.remove(socketIndex);
    QTcpSocket *clientSocket = socketData->getSocket();
    mapperRead->removeMappings(clientSocket);
    mapperClose->removeMappings(clientSocket);

    qDebug() << "Connection closed" << clientSocket->localAddress() << ":"
             << clientSocket->localPort() << " <-> " << clientSocket->peerAddress()
             << ":" << clientSocket->peerPort();

    delete socketData;
}

void ConnectionManager::readData(int socketIndex) {
    SocketData *socketData = socketMap[socketIndex];
    QTcpSocket *clientSocket = socketData->getSocket();

    while(clientSocket->bytesAvailable() > 0) {
        if (!socketData->isSizeRead()) {
            quint64 bytesRead = clientSocket->read((char *) socketData->getSizeBuff(),
                                                   PACKET_LENGTH_BYTE_SIZE - socketData->getSizeBuffRead());

            socketData->increaseSizeBuffRead(bytesRead);
            if (socketData->isSizeRead()) {
                socketData->prepareDataBuff();
            }
        } else {
            quint64 bytesRead = clientSocket->read((char *) socketData->getDataBuff(),
                                                   socketData->getSize() - socketData->getDataBuffRead());

            socketData->increaseDataBuffRead(bytesRead);
            if (socketData->isDataRead()) {
                socketData->resetReadBytesCount();
                processPacketDataRead(socketData->getDataBuff(), socketData->getSize(), socketIndex);
            }
        }
    }
}

void ConnectionManager::processPacketDataRead(unsigned char * data, quint32 length, int /*socketIndex*/) {

    QByteArray packetData((char *) data, length);

    qDebug() << "<---------------------";
    qDebug() << "Packet data read";
    qDebug() << packetData;
    qDebug() << "<---------------------";

    AbstractPacket *packet = packetReaderWriter.readPacket(packetData);

    if (packet == NULL) {
        qCritical() << "Packet parsing error";
        return;
    }

    emit packetReceived(packet);
}

void ConnectionManager::sendPacket(AbstractPacket * packet) {
    QTcpSocket * socket = getClientSocket(packet->getReceiverAddress(), packet->getReceiverPort());

    QByteArray packetData;
    packetReaderWriter.writePacketData(packet, packetData);

    quint32 length = packetData.length();
    char * buff = new char[sizeof(quint32)];
    qToBigEndian(length, (uchar *) buff);

    bool error = false;
    int bytesSend = 0;
    while (bytesSend < (int) sizeof(quint32)) {
        int send = socket->write(buff, sizeof(quint32));

        if (send < 0) {
            qWarning() << "Socket write error " << socket->errorString();
            error = true;
            break;
        }

        bytesSend += send;
    }

    bytesSend = 0;
    while (!error && bytesSend < (int) length) {
        int send = socket->write(packetData);

        if (send < 0) {
            qWarning() << "Socket write error " << socket->errorString();
            error = true;
            break;
        }

        bytesSend += send;
    }

    delete buff;

    if (error) {
        emit socketClosed(packet->getReceiverAddress(), packet->getReceiverPort());
        return;
    }

    socket->flush();

    qDebug() << "<---------------------";
    qDebug() << "Packet send " << packet->getReceiverAddress() << ":" << packet->getReceiverPort();
    qDebug() << packetData;
    qDebug() << "<---------------------";
}

QTcpSocket * ConnectionManager::getClientSocket(QString address, quint16 port) {
    QMap<int, SocketData*>::iterator it;
    for (it = socketMap.begin(); it != socketMap.end(); it++) {
        SocketData *socketData = it.value();
        if (address == socketData->getListenAddress() && port == socketData->getListenPort()) {
            return socketData->getSocket();
        }
    }

    qDebug() << "Connecting to socket " << address << ":" << port;
    QTcpSocket *socket = new QTcpSocket(this);
    socket->connectToHost(QHostAddress(address), port);
    if (socket->waitForConnected(5000)) {
        qDebug() << "Connect to host complete successfull. "
                 <<  socket->localAddress() << ":" << socket->localPort() << " <-> "
                 << socket->peerAddress() << ":" << socket->peerPort();
    } else {
        qCritical() << "Connect to host filed, reason: " << socket->errorString();
        return NULL;
    }

    int socketIndex = socketIndexCounter++;
    SocketData * socketData = new SocketData(socket);
    socketData->setListenAddress(address);
    socketData->setListenPort(port);
    socketMap[socketIndex]  = socketData;

    if (mapperRead == NULL) {
        mapperRead = new QSignalMapper(this);
        mapperClose = new QSignalMapper(this);
    }
    mapperRead->setMapping(socket, socketIndex);
    mapperClose->setMapping(socket, socketIndex);
    connect(socket, SIGNAL(readyRead()), mapperRead, SLOT(map()));
    connect(socket, SIGNAL(aboutToClose()), mapperClose, SLOT(map()));

    connect(mapperRead, SIGNAL(mapped(int)), this, SLOT(readData(int)));
    connect(mapperClose, SIGNAL(mapped(int)), this, SLOT(connectionClosed(int)));

    return socket;
}
